// Copyright (c) XiGuan 2023
// Author： xiguan
// Email: xiguan.teng@qq.com
// Create on 2023/11/20
// TODO:
//

#include "HttpResponse.h"

#include <fcntl.h>
#include <unistd.h>

const std::unordered_map<std::string, std::string> HttpResponse::SUFFIX_TYPE = {
    {".html", "text/html"},
    {".xml", "text/xml"},
    {".xhtml", "application/xhtml+xml"},
    {".txt", "text/plain"},
    {".rtf", "application/rtf"},
    {".pdf", "application/pdf"},
    {".word", "application/nsword"},
    {".png", "image/png"},
    {".gif", "image/gif"},
    {".jpg", "image/jpeg"},
    {".jpeg", "image/jpeg"},
    {".au", "audio/basic"},
    {".mpeg", "video/mpeg"},
    {".mpg", "video/mpeg"},
    {".avi", "video/x-msvideo"},
    {".gz", "application/x-gzip"},
    {".tar", "application/x-tar"},
    {".css", "text/css "},
    {".js", "text/javascript "},
};

const std::unordered_map<int, std::string> HttpResponse::CODE_STATUS = {
    {200, "OK"},
    {400, "Bad Request"},
    {403, "Forbidden"},
    {404, "Not Found"},
};

const std::unordered_map<int, std::string> HttpResponse::CODE_PATH = {
    {400, "/400.html"},
    {403, "/403.html"},
    {404, "/404.html"},
};

int HttpResponse::getCode() const { return code_; }

HttpResponse::HttpResponse() {
  code_ = -1;
  path_ = "";
  srcdir_ = "";

  is_keep_alive_ = false;
  mm_file_ = nullptr;
  mm_file_stat_ = {0};
}

HttpResponse::~HttpResponse() { unmap_file(); }

void HttpResponse::init(const std::string &srcdir, std::string &path,
                        bool is_keep_alive, int code) {
  assert(srcdir.size() > 0);

  if (mm_file_ != nullptr) {
    unmap_file();
  }
  code_ = code;
  is_keep_alive_ = is_keep_alive;
  path_ = path;
  srcdir_ = srcdir;
  mm_file_ = nullptr;
  mm_file_stat_ = {0};
}

void HttpResponse::unmap_file() {
  if (mm_file_ != nullptr) {
    munmap(mm_file_, mm_file_stat_.st_size);
    mm_file_ = nullptr;
  }
}

void HttpResponse::make_response(Buffer &buffer) {
  // 判断请求的资源文件
  if (stat((srcdir_ + path_).data(), &mm_file_stat_) < 0 ||
      S_ISDIR(mm_file_stat_.st_mode)) {
    code_ = 404;
  } else if (!(mm_file_stat_.st_mode & S_IROTH)) {
    code_ = 403;
  } else if (code_ == -1) {
    code_ = 200;
  }
  error_html_();
  add_state_line_(buffer);
  add_header_(buffer);
  add_content_(buffer);
}
char *HttpResponse::getMmFile() const { return mm_file_; }

void HttpResponse::error_html_() {
  if (CODE_PATH.count(code_) == 1) {
    path_ = CODE_PATH.find(code_)->second;
    stat((srcdir_ + path_).data(), &mm_file_stat_);
  }
}

void HttpResponse::add_state_line_(Buffer &buffer) {
  std::string status;
  code_ = CODE_STATUS.count(code_) == 1 ? code_ : 400;
  buffer.Append("HTTP/1.1 " + std::to_string(code_) + " " + status + "\r\n");
}

void HttpResponse::add_header_(Buffer &buffer) {
  buffer.Append("Connection: ");
  if (is_keep_alive_ == true) {
    buffer.Append("keep-alive\r\n");
    buffer.Append("keep-alive: max=6, timeout=120\r\n");
  } else {
    buffer.Append("close\r\n");
  }
  buffer.Append("Content-Type: " + get_file_type_() + "\r\n");
}

void HttpResponse::add_content_(Buffer &buffer) {
  LOG_DEBUG("[%s]:[%d]  file path %s.\n", __func__, __LINE__,
            (srcdir_ + path_).c_str());
  int src_file_fd = open((srcdir_ + path_).data(), O_RDONLY);
  if (src_file_fd < 0) {
    error_content(buffer, "File NotFound!");
    return;
  }

  int *mm_ret = (int *)mmap(0, mm_file_stat_.st_size, PROT_READ, MAP_PRIVATE,
                            src_file_fd, 0);
  if (*mm_ret == -1) {
    error_content(buffer, "File NotFound!");
    return;
  }
  mm_file_ = (char *)mm_ret;
  close(src_file_fd);
  buffer.Append("Content-length: " + std::to_string(mm_file_stat_.st_size) +
                "\r\n\r\n");
}

std::string HttpResponse::get_file_type_() {
  std::string::size_type idx = path_.find_last_of('.');

  std::string suffix = path_.substr(idx);
  if (SUFFIX_TYPE.count(suffix) == 1) {
    return SUFFIX_TYPE.find(suffix)->second;
  }
  return "text/plain";
}

void HttpResponse::error_content(Buffer &buffer, std::string message) {
  std::string body;
  std::string status;

  body += "<html><title>Error</title>";
  body += "<body bgcolor=\"ffffff\">";
  if (CODE_STATUS.count(code_) == 1) {
    status = CODE_STATUS.find(code_)->second;
  } else {
    status = "Bad Request";
  }
  body += std::to_string(code_) + " : " + status + "\n";
  body += "<p>" + message + "</p>";
  body += "<hr><em>TinyWebServer</em></body></html>";

  buffer.Append("Content-length: " + std::to_string(body.size()) + "\r\n\r\n");
  buffer.Append(body);
}

size_t HttpResponse::file_len() const { return mm_file_stat_.st_size; }
